{
  "cells": [
    {
      "metadata": {
        "id": "-n3Rngfddxii"
      },
      "cell_type": "markdown",
      "source": [
        "This notebook is a basic tutorial that demonstrates how to configure a simulation using Concordia."
      ]
    },
    {
      "metadata": {
        "id": "T6qSDTZtdeXk"
      },
      "cell_type": "markdown",
      "source": [
        "<a href=\"https://colab.research.google.com/github/google-deepmind/concordia/blob/main/examples/marketplace.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "metadata": {
        "id": "o4NhU-Z6cbop"
      },
      "cell_type": "code",
      "source": [
        "# @title Colab-specific setup (use a CodeSpace to avoid the need for this).\n",
        "try:\n",
        "  %env COLAB_RELEASE_TAG\n",
        "except:\n",
        "  pass  # Not running in colab.\n",
        "else:\n",
        "  %pip install --ignore-requires-python --requirement 'https://raw.githubusercontent.com/google-deepmind/concordia/main/examples/requirements.in' 'git+https://github.com/google-deepmind/concordia.git#egg=gdm-concordia'\n",
        "  %pip list"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "R8N4hGX5ci_d"
      },
      "cell_type": "code",
      "source": [
        "# @title Imports\n",
        "\n",
        "from collections.abc import Sequence\n",
        "import random\n",
        "from typing import Any, Dict, List\n",
        "\n",
        "from concordia.components import agent as actor_components\n",
        "from concordia.components import game_master as gm_components\n",
        "from concordia.contrib import language_models as language_model_utils\n",
        "from concordia.contrib.components.game_master import marketplace\n",
        "from concordia.environment.engines import simultaneous\n",
        "import concordia.prefabs.entity as entity_prefabs\n",
        "import concordia.prefabs.game_master as game_master_prefabs\n",
        "from concordia.prefabs.simulation import generic as simulation\n",
        "from concordia.typing import prefab as prefab_lib\n",
        "from concordia.utils import helper_functions\n",
        "from IPython import display\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import seaborn as sns\n",
        "import sentence_transformers"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "a9Pb-LdTcOeZ"
      },
      "cell_type": "code",
      "source": [
        "# @title Language Model Selection: provide key or select DISABLE_LANGUAGE_MODEL\n",
        "\n",
        "# By default this colab uses models via an external API so you must provide an\n",
        "# API key. TogetherAI offers open weights models from all sources.\n",
        "\n",
        "API_KEY = ''  # @param {type: 'string'}\n",
        "# See concordia/language_model/utils.py\n",
        "API_TYPE = 'openai'  # e.g. 'together_ai' or 'openai'.\n",
        "MODEL_NAME = (  # for API_TYPE = 'together_ai', we recommend MODEL_NAME = 'google/gemma-3-27b-it'\n",
        "    'gpt-5'\n",
        ")\n",
        "# To debug without spending money on API calls, set DISABLE_LANGUAGE_MODEL=True\n",
        "DISABLE_LANGUAGE_MODEL = False"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Za2zOpeTci8_"
      },
      "cell_type": "code",
      "source": [
        "# @title Use the selected language model\n",
        "\n",
        "# Note that it is also possible to use local models or other API models,\n",
        "# simply replace this cell with the correct initialization for the model\n",
        "# you want to use.\n",
        "\n",
        "if not DISABLE_LANGUAGE_MODEL and not API_KEY:\n",
        "  raise ValueError('API_KEY is required.')\n",
        "\n",
        "model = language_model_utils.language_model_setup(\n",
        "    api_type=API_TYPE,\n",
        "    model_name=MODEL_NAME,\n",
        "    api_key=API_KEY,\n",
        "    disable_language_model=DISABLE_LANGUAGE_MODEL,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "KC0dbTwLci6k"
      },
      "cell_type": "code",
      "source": [
        "# @title Setup sentence encoder\n",
        "\n",
        "if DISABLE_LANGUAGE_MODEL:\n",
        "  embedder = lambda _: np.ones(3)\n",
        "else:\n",
        "  st_model = sentence_transformers.SentenceTransformer(\n",
        "      'sentence-transformers/all-mpnet-base-v2'\n",
        "  )\n",
        "  embedder = lambda x: st_model.encode(x, show_progress_bar=False)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "yN8NEgajci3_"
      },
      "cell_type": "code",
      "source": [
        "test = model.sample_text(\n",
        "    'Is societal and technological progress like getting a clearer picture of '\n",
        "    'something true and deep?'\n",
        ")\n",
        "print(test)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Ya7BvCG1cwNY"
      },
      "cell_type": "code",
      "source": [
        "# @title Load prefabs from packages to make the specific palette to use here.\n",
        "\n",
        "prefabs = {\n",
        "    **helper_functions.get_package_classes(entity_prefabs),\n",
        "    **helper_functions.get_package_classes(game_master_prefabs),\n",
        "}"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "TSB7w33mRMOj"
      },
      "cell_type": "code",
      "source": [
        "# @title Print menu of prefabs\n",
        "\n",
        "display.display(\n",
        "    display.Markdown(helper_functions.print_pretty_prefabs(prefabs))\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KfT20U8eX-KI"
      },
      "outputs": [],
      "source": [
        "# @title Domain objects for marketplace simulations\n",
        "\n",
        "Good = marketplace.Good\n",
        "MarketplaceAgent = marketplace.MarketplaceAgent\n",
        "MarketPlace = marketplace.MarketPlace\n",
        "\n",
        "\n",
        "def make_goods() -> List[Good]:\n",
        "  categories = [\"Food\", \"Clothing\", \"Accessories\", \"Gadgets\"]\n",
        "  qualities = [\"Low\", \"Mid\", \"High\"]\n",
        "  return [Good(c, q, c + \"_\" + q) for c in categories for q in qualities]\n",
        "\n",
        "\n",
        "def make_agents(\n",
        "    n: int, goods: Sequence[Good], names: Sequence[str], seed: int = 123\n",
        ") -> List[MarketplaceAgent]:\n",
        "  rng = random.Random(seed)\n",
        "  agents: List[MarketplaceAgent] = []\n",
        "  for i in range(n):\n",
        "    role = \"producer\" if i < n // 2 else \"consumer\"\n",
        "    inv_good = rng.choice(goods)\n",
        "    inventory = {inv_good.id: rng.randint(5, 15)} if role == \"producer\" else {}\n",
        "    agents.append(\n",
        "        MarketplaceAgent(\n",
        "            name=names[i],\n",
        "            role=role,\n",
        "            cash=50.0,\n",
        "            inventory=inventory,\n",
        "            queue=[],\n",
        "        )\n",
        "    )\n",
        "  return agents"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wZsYC-wIW9-P"
      },
      "source": [
        "# 4 Actor Simulation"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sVLDO1DeiN6X"
      },
      "outputs": [],
      "source": [
        "goods = make_goods()\n",
        "names = ['Alex', 'Nicole', 'Jeremy', 'Megan']\n",
        "agents = make_agents(4, goods, names)\n",
        "\n",
        "component_kwargs = {\n",
        "    'components': [\n",
        "        actor_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY\n",
        "    ],\n",
        "    'agents': agents,\n",
        "    'goods': goods,\n",
        "}\n",
        "\n",
        "prefabs = {\n",
        "    **helper_functions.get_package_classes(entity_prefabs),\n",
        "    **helper_functions.get_package_classes(game_master_prefabs),\n",
        "}\n",
        "\n",
        "PLAYER_ONE = names[0]\n",
        "PLAYER_TWO = names[1]\n",
        "PLAYER_THREE = names[2]\n",
        "PLAYER_FOUR = names[3]\n",
        "\n",
        "instances = [\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_ONE,\n",
        "            'goal': (\n",
        "                'Your goal is to sell your stock of Food_Low for a profit. Your'\n",
        "                ' cost to produce each unit is $2.00. You must try to sell for'\n",
        "                ' more than $2.00 to be profitable. You will not accept a price'\n",
        "                ' lower than your cost.'\n",
        "            ),\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_TWO,\n",
        "            'goal': (\n",
        "                'Your goal is to sell your stock of Food_Low for a profit. You'\n",
        "                ' are a very efficient producer, so your cost for each unit is'\n",
        "                ' only $1.50. You must sell for more than $1.50, but you can'\n",
        "                ' afford to undercut less efficient sellers.'\n",
        "            ),\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_THREE,\n",
        "            'goal': (\n",
        "                'Your goal is to acquire Food_Low for your own use. You value'\n",
        "                ' it highly, so you are willing to pay up to $4.50 per unit,'\n",
        "                ' but you will try to get it for as cheap as possible. You will'\n",
        "                ' not bid higher than $4.50.'\n",
        "            ),\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_FOUR,\n",
        "            'goal': (\n",
        "                'Your goal is to acquire Food_Low. It is useful, but not'\n",
        "                ' essential. You are willing to pay up to $3.75 per unit, but'\n",
        "                ' you would prefer to pay much less. You will not bid higher'\n",
        "                ' than $3.75.'\n",
        "            ),\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='marketplace__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'MarketplaceGM',\n",
        "            'experiment_component_class': MarketPlace,\n",
        "            'experiment_component_init_kwargs': component_kwargs,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='formative_memories_initializer__GameMaster',\n",
        "        role=prefab_lib.Role.INITIALIZER,\n",
        "        params={\n",
        "            'name': 'initial setup rules',\n",
        "            'next_game_master_name': 'MarketplaceGM',\n",
        "            'shared_memories': [\n",
        "                (\n",
        "                    'There is a small town of Smallville where'\n",
        "                    f' {PLAYER_ONE} and {PLAYER_TWO} grew up.'\n",
        "                ),\n",
        "            ],\n",
        "        },\n",
        "    ),\n",
        "]\n",
        "\n",
        "default_premise = \"\"\"You are in a marketplace that buys and sells goods\n",
        "\"\"\"\n",
        "\n",
        "config = prefab_lib.Config(\n",
        "    default_premise=default_premise,\n",
        "    default_max_steps=5,\n",
        "    prefabs=prefabs,\n",
        "    instances=instances,\n",
        ")\n",
        "\n",
        "engine = simultaneous.Simultaneous()\n",
        "\n",
        "#  Initialize the simulation\n",
        "simul_sim = simulation.Simulation(\n",
        "    config=config, model=model, embedder=embedder, engine=engine\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bCJeqMp8jr2u"
      },
      "outputs": [],
      "source": [
        "# @title Run the simulation\n",
        "results_log = simul_sim.play()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T7E1p2RNokEB"
      },
      "outputs": [],
      "source": [
        "# @title Display the log\n",
        "display.HTML(results_log)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s8ZuvzB5t6n-"
      },
      "source": [
        "# More Realistic Market of 10 Actors"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# @title Define real goods\n",
        "\n",
        "DETAILED_GOODS = {\n",
        "    \"Food\": {\n",
        "        \"Low\": {\n",
        "            \"Street Taco\": {\n",
        "                \"price\": 2.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Authentic flavor, unbeatable price! Perfectly seasoned,\"\n",
        "                    \" grilled to order, and served on a warm corn tortilla. The\"\n",
        "                    \" true taste of the street.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Cup Noodles\": {\n",
        "                \"price\": 2.5,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Quick, easy, and delicious. Your perfect savory meal is\"\n",
        "                    \" just three minutes away. Just add hot water and enjoy.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"Mid\": {\n",
        "            \"Gourmet Burger\": {\n",
        "                \"price\": 15.0,\n",
        "                \"inventory\": 50,\n",
        "                \"advert\": (\n",
        "                    \"A burger experience like no other. A juicy, thick-cut\"\n",
        "                    \" patty made with premium, locally-sourced beef, served on\"\n",
        "                    \" a toasted brioche bun.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Sushi Platter\": {\n",
        "                \"price\": 22.0,\n",
        "                \"inventory\": 40,\n",
        "                \"advert\": (\n",
        "                    \"A fresh and vibrant selection of our finest rolls and\"\n",
        "                    \" nigiri. A taste of Japan in every artfully prepared bite.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"High\": {\n",
        "            \"Michelin Star Meal\": {\n",
        "                \"price\": 75.0,\n",
        "                \"inventory\": 10,\n",
        "                \"advert\": (\n",
        "                    \"An unforgettable culinary journey. Allow our celebrated\"\n",
        "                    \" chef to present a multi-course tasting menu that will\"\n",
        "                    \" delight your senses.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Omakase Experience\": {\n",
        "                \"price\": 90.0,\n",
        "                \"inventory\": 8,\n",
        "                \"advert\": (\n",
        "                    \"Trust the chef. An intimate, curated selection of the\"\n",
        "                    \" finest seasonal fish and delicacies, flown in daily and\"\n",
        "                    \" prepared before your eyes.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "    },\n",
        "    \"Clothing\": {\n",
        "        \"Low\": {\n",
        "            \"Brown Fast Fashion T-Shirt\": {\n",
        "                \"price\": 5.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Trendy, affordable, and effortlessly cool. Update your\"\n",
        "                    \" style for any season without breaking the bank.\"\n",
        "                ),\n",
        "            },\n",
        "            \"White Cotton T-Shirt\": {\n",
        "                \"price\": 6.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"The essential classic for any wardrobe. Made from 100%\"\n",
        "                    \" soft, breathable cotton for a perfect, comfortable fit\"\n",
        "                    \" every time.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"Mid\": {\n",
        "            \"Levi's Jeans\": {\n",
        "                \"price\": 80.0,\n",
        "                \"inventory\": 50,\n",
        "                \"advert\": (\n",
        "                    \"Quality never goes out of style. The original, iconic blue\"\n",
        "                    \" jean, crafted from durable denim for a timeless look and\"\n",
        "                    \" feel.\"\n",
        "                ),\n",
        "            },\n",
        "            \"AllSaints Leather Jacket\": {\n",
        "                \"price\": 100.0,\n",
        "                \"inventory\": 25,\n",
        "                \"advert\": (\n",
        "                    \"Define your edge. Cut from supple, premium lambskin\"\n",
        "                    \" leather with signature metal hardware. A timeless\"\n",
        "                    \" investment piece.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"High\": {\n",
        "            \"Armani Suit\": {\n",
        "                \"price\": 1200.0,\n",
        "                \"inventory\": 10,\n",
        "                \"advert\": (\n",
        "                    \"Exude confidence and power. Impeccable Italian tailoring\"\n",
        "                    \" and luxurious fabrics combine for a silhouette that\"\n",
        "                    \" commands attention.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Burberry Jacket\": {\n",
        "                \"price\": 2500.0,\n",
        "                \"inventory\": 5,\n",
        "                \"advert\": (\n",
        "                    \"Iconic British luxury. The ultimate statement in\"\n",
        "                    \" outerwear, featuring the classic check and unparalleled\"\n",
        "                    \" craftsmanship.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "    },\n",
        "    \"Gadgets\": {\n",
        "        \"Low\": {\n",
        "            \"Basic Headphones\": {\n",
        "                \"price\": 15.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Your daily audio companion. Delivers crisp, clear sound\"\n",
        "                    \" for your music, podcasts, and calls, wherever you go.\"\n",
        "                ),\n",
        "            },\n",
        "            \"USB Power Bank\": {\n",
        "                \"price\": 20.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Never run out of power again. This compact and reliable\"\n",
        "                    \" power bank ensures your devices stay charged on the go.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"Mid\": {\n",
        "            \"Kindle E-book Reader\": {\n",
        "                \"price\": 200.0,\n",
        "                \"inventory\": 40,\n",
        "                \"advert\": (\n",
        "                    \"Carry your library in one hand. A glare-free display that\"\n",
        "                    \" reads like real paper, even in direct sunlight. Read\"\n",
        "                    \" anytime, anywhere.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Apple AirPods Pro\": {\n",
        "                \"price\": 225.0,\n",
        "                \"inventory\": 30,\n",
        "                \"advert\": (\n",
        "                    \"Magic, remastered. Experience a new level of immersive\"\n",
        "                    \" sound with active noise cancellation and intuitive touch\"\n",
        "                    \" controls.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"High\": {\n",
        "            \"Macbook Laptop\": {\n",
        "                \"price\": 1800.0,\n",
        "                \"inventory\": 15,\n",
        "                \"advert\": (\n",
        "                    \"Power to the pro. Supercharged by the latest chip, this\"\n",
        "                    \" laptop delivers game-changing performance for your\"\n",
        "                    \" biggest ideas.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Sony 4K TV\": {\n",
        "                \"price\": 2000.0,\n",
        "                \"inventory\": 10,\n",
        "                \"advert\": (\n",
        "                    \"Picture-perfect reality. Experience breathtaking color,\"\n",
        "                    \" contrast, and clarity that brings entertainment to life\"\n",
        "                    \" in stunning 4K.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "    },\n",
        "    \"Accessories\": {\n",
        "        \"Low\": {\n",
        "            \"Scaly the Lizard Beanie Baby\": {\n",
        "                \"price\": 8.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"A collectible friend for all ages! This rare, retired\"\n",
        "                    \" Beanie Baby is a must-have for any nostalgic collector.\"\n",
        "                    \" Don't miss out!\"\n",
        "                ),\n",
        "            },\n",
        "            \"Temu Watch\": {\n",
        "                \"price\": 10.0,\n",
        "                \"inventory\": 100,\n",
        "                \"advert\": (\n",
        "                    \"Stylish and surprisingly affordable. Get the look of a\"\n",
        "                    \" high-end watch for a fraction of the price. Why pay more?\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"Mid\": {\n",
        "            \"Stanley Cup Quencher Tumbler\": {\n",
        "                \"price\": 45.0,\n",
        "                \"inventory\": 50,\n",
        "                \"advert\": (\n",
        "                    \"The hydration must-have you never knew you needed. Made\"\n",
        "                    \" from recycled stainless steel with vacuum insulation to\"\n",
        "                    \" keep drinks cold for hours. Features an advanced\"\n",
        "                    \" FlowState lid, an easy-carry handle, and a car cup\"\n",
        "                    \" holder-friendly base.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Longchamp Le Pliage Bag\": {\n",
        "                \"price\": 100.0,\n",
        "                \"inventory\": 40,\n",
        "                \"advert\": (\n",
        "                    \"Effortless Parisian chic. This iconic, foldable bag is the\"\n",
        "                    \" perfect, lightweight companion for work, travel, and\"\n",
        "                    \" every day.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "        \"High\": {\n",
        "            \"Chanel Handbag\": {\n",
        "                \"price\": 1800.0,\n",
        "                \"inventory\": 8,\n",
        "                \"advert\": (\n",
        "                    \"An icon of elegance. The ultimate accessory for the\"\n",
        "                    \" discerning fashionista. Rectangular shape, braided chain\"\n",
        "                    \" strap, brand monogram clasp, double flap design, double\"\n",
        "                    \" compartment, slip pockets at interior, polished hardware.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Rolex Watch\": {\n",
        "                \"price\": 2000.0,\n",
        "                \"inventory\": 5,\n",
        "                \"advert\": (\n",
        "                    \"A crown for every achievement. Meticulously crafted from\"\n",
        "                    \" Oystersteel and precious metals, this is more than a\"\n",
        "                    \" timepiece—it is a legacy.\"\n",
        "                ),\n",
        "            },\n",
        "            \"Pop Mart Labubu Monster Vinyl Plush Doll (Limited Edition)\": {\n",
        "                \"price\": 280.0,\n",
        "                \"inventory\": 15,\n",
        "                \"advert\": (\n",
        "                    \"Extremely rare collector's item! Designed by Kasing Lung,\"\n",
        "                    \" this limited-edition Labubu is the must-have for any\"\n",
        "                    \" serious art toy enthusiast.\"\n",
        "                ),\n",
        "            },\n",
        "        },\n",
        "    },\n",
        "}"
      ],
      "metadata": {
        "id": "mHFpNJG5Bzen"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "def make_agents_from_data(\n",
        "    agent_data_list: List[Dict[str, Any]],\n",
        ") -> List[MarketplaceAgent]:\n",
        "  \"\"\"Creates a list of MarketplaceAgent objects from agent data dicts.\"\"\"\n",
        "  agents: List[MarketplaceAgent] = []\n",
        "  for agent_info in agent_data_list:\n",
        "    inventory = agent_info.get(\"inventory\", {})\n",
        "    role = agent_info[\"type\"]\n",
        "    cash = float(agent_info.get(\"cash\", 100.0))\n",
        "\n",
        "    if role == \"producer\":\n",
        "      inventory = {agent_info[\"good_to_sell\"]: agent_info[\"inventory\"]}\n",
        "    elif role == \"consumer\":\n",
        "      # make sure there's one piece of clothing in the inventory\n",
        "      assert inventory, \"Inventory should not be empty for consumer.\"\n",
        "\n",
        "    agents.append(\n",
        "        MarketplaceAgent(\n",
        "            name=agent_info[\"name\"],\n",
        "            role=role,\n",
        "            cash=cash,\n",
        "            inventory=inventory,\n",
        "            queue=[],\n",
        "        )\n",
        "    )\n",
        "  return agents\n",
        "\n",
        "\n",
        "def get_all_goods_from_spec(\n",
        "    spec: Dict[str, Dict[str, Dict[str, Dict[str, Any]]]],\n",
        ") -> List[Good]:\n",
        "  \"\"\"Creates a flat list of Good objects from the detailed specification.\"\"\"\n",
        "  goods_list: List[Good] = []\n",
        "  for category, qualities in spec.items():\n",
        "    for quality, items in qualities.items():\n",
        "      for item_name, details in items.items():\n",
        "        # Use .get() for optional attributes to avoid errors if they are missing\n",
        "        price = details.get(\"price\")\n",
        "        inventory = details.get(\"inventory\")\n",
        "        advert = details.get(\"advert\")\n",
        "\n",
        "        goods_list.append(\n",
        "            Good(\n",
        "                category=category,\n",
        "                quality=quality,\n",
        "                id=item_name,\n",
        "                price=price,\n",
        "                inventory=inventory,\n",
        "                advert=advert,\n",
        "            )\n",
        "        )\n",
        "  return goods_list\n",
        "\n",
        "\n",
        "goods = get_all_goods_from_spec(DETAILED_GOODS)"
      ],
      "metadata": {
        "id": "umrO8AI0B8vJ"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "names = [\n",
        "    'Alex',\n",
        "    'Nicole',\n",
        "    'Jeremy',\n",
        "    'Megan',\n",
        "    'David',\n",
        "    'Samantha',\n",
        "    'Chris',\n",
        "    'Jessica',\n",
        "    'Ryan',\n",
        "    'Lauren',\n",
        "    'Alice',\n",
        "    'Bob',\n",
        "    'Charlie',\n",
        "    'Drake',\n",
        "    'Eve',\n",
        "    'Frank',\n",
        "    'Grace',\n",
        "    'Heidi',\n",
        "    'Ivan',\n",
        "    'Judy',\n",
        "]\n",
        "\n",
        "num_rounds = 10\n",
        "num_items = 1\n",
        "num_agents = 10\n",
        "num_sellers = num_agents // 2\n",
        "num_buyers = num_agents - num_sellers\n",
        "\n",
        "# --- Programmatically Generate num_agent Agent Profiles ---\n",
        "random.seed(42)  # For reproducible assignments\n",
        "\n",
        "if num_agents > len(names):\n",
        "  # Generate generic names if there are more agents than unique names\n",
        "  print(\n",
        "      f'Info: {num_agents} agents > {len(names)} names. Generating generic'\n",
        "      ' names.'\n",
        "  )\n",
        "  seller_profiles = [{'name': f'Seller_{i+1}'} for i in range(num_sellers)]\n",
        "  buyer_profiles = [{'name': f'Buyer_{i+1}'} for i in range(num_buyers)]\n",
        "else:\n",
        "  # Use the predefined list of names if sufficient\n",
        "  print(f'Info: Using {num_agents} predefined names for agents.')\n",
        "  random.shuffle(names)\n",
        "  seller_names = names[:num_sellers]\n",
        "  buyer_names = names[num_sellers:num_agents]\n",
        "\n",
        "  seller_profiles = [{'name': name} for name in seller_names]\n",
        "  buyer_profiles = [{'name': name} for name in buyer_names]\n",
        "\n",
        "# Add randomized efficiencies to sellers\n",
        "for seller in seller_profiles:\n",
        "  seller['efficiency'] = round(random.uniform(0.8, 1.3), 2)\n",
        "\n",
        "# Generate 50 buyer profiles with cash distributed using a Pareto (power-law) distribution.\n",
        "min_cash = 50.0  # The scale parameter (x_m) of the distribution.\n",
        "# The shape parameter (alpha). Values > 1 are typical. Smaller values create a more extreme tail (more inequality).\n",
        "# A value of ~1.16 represents the 80/20 rule (Pareto principle). We use 1.5 for a strong, but not extreme, skew.\n",
        "distribution_shape = 1.5\n",
        "# An effective maximum to prevent unrealistic outlier values in the simulation.\n",
        "max_cash_cap = 25000.0\n",
        "\n",
        "# Generate values from the Pareto distribution. The formula (np.random.pareto(a) + 1) * xm\n",
        "# generates values with a minimum of xm. We generate all 50 values at once.\n",
        "cash_values = np.random.pareto(distribution_shape, num_buyers) * min_cash\n",
        "# Clip the values to our defined maximum cap to prevent outliers from breaking the simulation.\n",
        "cash_values = np.clip(cash_values, min_cash, max_cash_cap)\n",
        "\n",
        "# Add cash to buyers\n",
        "for i, buyer in enumerate(buyer_profiles):\n",
        "  buyer['cash'] = f'{round(cash_values[i], 2)}'\n",
        "\n",
        "# --- Select ONE CATEGORY for all producers ---\n",
        "# This focuses the market simulation on direct competition within a single category.\n",
        "selected_category = random.choice(list(DETAILED_GOODS.keys()))\n",
        "print(f'--- Market category selected for this run: {selected_category} ---')\n",
        "\n",
        "# Get the list of producible goods within the selected category\n",
        "producible_goods_in_category = [\n",
        "    (selected_category, qual, item_name, details)\n",
        "    for qual, items in DETAILED_GOODS[selected_category].items()\n",
        "    for item_name, details in items.items()\n",
        "]\n",
        "producible_goods_in_category = producible_goods_in_category[:num_items]\n",
        "\n",
        "# --- Programmatically process agents to assign goods and calculate costs ---\n",
        "agent_data = []\n",
        "\n",
        "# Assign goods and costs to the 50 sellers\n",
        "for seller in seller_profiles:\n",
        "  category, quality, item_name, good_details = random.choice(\n",
        "      producible_goods_in_category\n",
        "  )\n",
        "\n",
        "  seller_data = seller.copy()\n",
        "  seller_data['type'] = 'producer'\n",
        "  seller_data['good_to_sell'] = item_name\n",
        "  # Cost = base_cost of the item * seller's personal efficiency modifier\n",
        "  seller_data['production_cost'] = round(\n",
        "      good_details['price'] * seller['efficiency'], 2\n",
        "  )\n",
        "  seller_data['inventory'] = (\n",
        "      10 * num_rounds\n",
        "  )  # Increased inventory for a longer simulation\n",
        "  agent_data.append(seller_data)\n",
        "\n",
        "# Add the 50 buyers to the final list\n",
        "for buyer in buyer_profiles:\n",
        "  buyer_data = buyer.copy()\n",
        "  buyer_data['type'] = 'consumer'\n",
        "  random_clothing = random.choice(\n",
        "      list(DETAILED_GOODS['Clothing']['Low'].keys())\n",
        "  )\n",
        "  buyer_data['inventory'] = {random_clothing: 1}\n",
        "  agent_data.append(buyer_data)\n",
        "\n",
        "# --- Setup Simulation Components with the new data ---\n",
        "names = [agent['name'] for agent in agent_data]\n",
        "goods = get_all_goods_from_spec(DETAILED_GOODS)\n",
        "agents = make_agents_from_data(agent_data)\n",
        "\n",
        "component_kwargs = {\n",
        "    'components': [\n",
        "        actor_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY\n",
        "    ],\n",
        "    'agents': agents,\n",
        "    'goods': goods,\n",
        "}\n",
        "\n",
        "# --- Programmatically Generate Agent Instances ---\n",
        "instances = []\n",
        "for agent in agent_data:\n",
        "  if agent['type'] == 'producer':\n",
        "    goal_text = (\n",
        "        f\"You are a seller of {agent['good_to_sell']}. Your cost to produce\"\n",
        "        f\" each unit is ${agent['production_cost']:.2f}. Your goal is to sell\"\n",
        "        ' your stock for a profit. You must sell for more than your cost to be'\n",
        "        ' profitable.'\n",
        "    )\n",
        "    prefab = 'basic__Entity'\n",
        "  else:  # Buyer\n",
        "    goal_text = (\n",
        "        f'You are a buyer at the marketplace. Your goal is to purchase goods'\n",
        "        f' that match your preference for items of a certain quality and'\n",
        "        f' category given your budget. You will try to buy them for a good'\n",
        "        f' value price.'\n",
        "    )\n",
        "    prefab = 'basic__Entity'\n",
        "\n",
        "  instance_config = prefab_lib.InstanceConfig(\n",
        "      prefab=prefab,\n",
        "      role=prefab_lib.Role.ENTITY,\n",
        "      params={'name': agent['name'], 'goal': goal_text},\n",
        "  )\n",
        "  instances.append(instance_config)\n",
        "\n",
        "# --- Add Game Master and Initializer Instances ---\n",
        "\n",
        "instances.append(\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='marketplace__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'MarketplaceGM',\n",
        "            'experiment_component_class': MarketPlace,\n",
        "            'experiment_component_init_kwargs': component_kwargs,\n",
        "        },\n",
        "    )\n",
        ")\n",
        "\n",
        "# Generic shared memories for the larger group\n",
        "all_player_names = ', '.join(names)\n",
        "instances.append(\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='formative_memories_initializer__GameMaster',\n",
        "        role=prefab_lib.Role.INITIALIZER,\n",
        "        params={\n",
        "            'name': 'initial setup rules',\n",
        "            'next_game_master_name': 'MarketplaceGM',\n",
        "            'shared_memories': [\n",
        "                (\n",
        "                    'There is a marketplace in Los Angeles where all'\n",
        "                    f' residents, including {all_player_names}, come to trade.'\n",
        "                ),\n",
        "                (\n",
        "                    'Some residents are producers of various goods, while'\n",
        "                    ' others are buyers.'\n",
        "                ),\n",
        "            ],\n",
        "        },\n",
        "    )\n",
        ")\n",
        "\n",
        "# --- Final Configuration and Simulation Initialization ---\n",
        "\n",
        "default_premise = \"\"\"You are in a marketplace that buys and sells goods for food, clothing, and gadgets of low, mid, and high quality. Interact with other participants to achieve your goals.\n",
        "\"\"\"\n",
        "\n",
        "config = prefab_lib.Config(\n",
        "    default_premise=default_premise,\n",
        "    default_max_steps=num_rounds,\n",
        "    prefabs=prefabs,\n",
        "    instances=instances,\n",
        ")\n",
        "\n",
        "engine = simultaneous.Simultaneous()\n",
        "\n",
        "#  Initialize the simulation\n",
        "realistic_sim = simulation.Simulation(\n",
        "    config=config, model=model, embedder=embedder, engine=engine\n",
        ")"
      ],
      "metadata": {
        "id": "6cAD9yHyC-i8"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fblTr_o5un57"
      },
      "outputs": [],
      "source": [
        "# @title Run the simulation\n",
        "\n",
        "results_log = realistic_sim.play()"
      ]
    },
    {
      "metadata": {
        "id": "oQvo3alwdMgE"
      },
      "cell_type": "code",
      "source": [
        "# @title Display the log\n",
        "display.HTML(results_log)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Plot Results"
      ],
      "metadata": {
        "id": "faydx0kSBaum"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YO453oMBbSMn",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "# @title Plotting Functions\n",
        "\n",
        "\n",
        "def find_equilibrium(prices, supply, demand):\n",
        "  \"\"\"Finds the equilibrium price and quantity from discrete supply and demand curves.\n",
        "\n",
        "  This function identifies the intersection point, which represents the\n",
        "  theoretical\n",
        "  market-clearing price and quantity.\n",
        "\n",
        "  Args:\n",
        "      prices (list): A sorted list of unique prices from the order book.\n",
        "      supply (list): The cumulative supply quantity at each price.\n",
        "      demand (list): The cumulative demand quantity at each price.\n",
        "\n",
        "  Returns:\n",
        "      tuple: A tuple of (equilibrium_price, equilibrium_quantity). Returns\n",
        "             (None, 0) if no trade is possible (i.e., the lowest ask price\n",
        "             is higher than the highest bid price).\n",
        "  \"\"\"\n",
        "  # Case 1: No overlap between supply and demand curves.\n",
        "  # This happens if the highest price a buyer is willing to pay is less than\n",
        "  # the lowest price a seller is willing to accept. No trades can occur.\n",
        "  # We detect this by checking if the maximum demand is zero at the lowest price\n",
        "  # where supply starts to appear.\n",
        "  if not any(d > 0 for d in demand) or not any(s > 0 for s in supply):\n",
        "    return None, 0  # No demand or no supply at all.\n",
        "\n",
        "  first_supply_idx = next((i for i, s in enumerate(supply) if s > 0), None)\n",
        "  if first_supply_idx is not None and demand[first_supply_idx] == 0:\n",
        "    return None, 0  # No demand at the minimum supply price.\n",
        "\n",
        "  # Case 2: Find the intersection point.\n",
        "  # We iterate through the prices to find the last point where demand is\n",
        "  # still greater than or equal to supply. This point represents the\n",
        "  # clearing price and quantity for all possible trades.\n",
        "  eq_price = None\n",
        "  eq_quantity = 0\n",
        "\n",
        "  for i in range(len(prices)):\n",
        "    if demand[i] >= supply[i]:\n",
        "      # The potential quantity that could be traded is the minimum\n",
        "      # of the supply and demand at this price point.\n",
        "      # However, since we are looking for the final clearing price,\n",
        "      # we consider the supply available at this level.\n",
        "      eq_quantity = supply[i]\n",
        "      eq_price = prices[i]\n",
        "    else:\n",
        "      # The first time demand drops below supply, we've found our\n",
        "      # crossover region. The previous point was the equilibrium.\n",
        "      break\n",
        "\n",
        "  return eq_price, eq_quantity\n",
        "\n",
        "\n",
        "def plot_supply_demand_history(\n",
        "    curve_history, empirical_price_history=None, good_id='Food_Low'\n",
        "):\n",
        "  \"\"\"Plots the supply and demand curves for each round in the history,\n",
        "\n",
        "  and optionally overlays the actual transaction prices.\n",
        "\n",
        "  Args:\n",
        "      curve_history (dict): History of supply/demand curves {round: {good:\n",
        "        {data}}}.\n",
        "      empirical_price_history (list, optional): A list of dicts with actual\n",
        "        transaction prices for each round. e.g., [{'Food_Low': 2.0}, ...].\n",
        "      good_id (str): The specific good to plot.\n",
        "  \"\"\"\n",
        "  num_rounds = len(curve_history)\n",
        "  if num_rounds == 0:\n",
        "    print('Curve history is empty. Nothing to plot.')\n",
        "    return\n",
        "\n",
        "  # Create subplots for each round.\n",
        "  cols = 3\n",
        "  rows = int(np.ceil(num_rounds / cols))\n",
        "  fig, axes = plt.subplots(\n",
        "      rows, cols, figsize=(cols * 5.5, rows * 5), squeeze=False\n",
        "  )\n",
        "  axes = axes.flatten()\n",
        "\n",
        "  for i, (round_idx, round_data) in enumerate(curve_history.items()):\n",
        "    ax = axes[i]\n",
        "\n",
        "    market_data = round_data.get(good_id, {})\n",
        "    prices = market_data.get('prices', [])\n",
        "    supply = market_data.get('supply', [])\n",
        "    demand = market_data.get('demand', [])\n",
        "\n",
        "    # Plot supply and demand curves\n",
        "    if prices:\n",
        "      ax.step(\n",
        "          supply,\n",
        "          prices,\n",
        "          where='post',\n",
        "          label='Supply',\n",
        "          color='cornflowerblue',\n",
        "          linewidth=2,\n",
        "      )\n",
        "      ax.step(\n",
        "          demand,\n",
        "          prices,\n",
        "          where='post',\n",
        "          label='Demand',\n",
        "          color='salmon',\n",
        "          linewidth=2,\n",
        "      )\n",
        "\n",
        "    # Find and plot the theoretical equilibrium point\n",
        "    eq_price, eq_quantity = find_equilibrium(prices, supply, demand)\n",
        "\n",
        "    title_str = f'Round {round_idx}\\n'\n",
        "    if eq_price is not None:\n",
        "      ax.plot(\n",
        "          eq_quantity, eq_price, 'ko', markersize=8, label=f'Theoretical Eq.'\n",
        "      )\n",
        "      ax.hlines(\n",
        "          y=eq_price,\n",
        "          xmin=0,\n",
        "          xmax=eq_quantity,\n",
        "          color='grey',\n",
        "          linestyle='--',\n",
        "          linewidth=1.5,\n",
        "      )\n",
        "      ax.vlines(\n",
        "          x=eq_quantity,\n",
        "          ymin=0,\n",
        "          ymax=eq_price,\n",
        "          color='grey',\n",
        "          linestyle='--',\n",
        "          linewidth=1.5,\n",
        "      )\n",
        "      title_str += f'Theoretical Eq: ${eq_price:.2f}, Qty: {eq_quantity}'\n",
        "    else:\n",
        "      title_str += 'No Trade Equilibrium'\n",
        "\n",
        "    # Plot the empirical price if available\n",
        "    if empirical_price_history and i < len(empirical_price_history):\n",
        "      emp_price = empirical_price_history[i].get(good_id)\n",
        "      if emp_price is not None and not np.isnan(emp_price):\n",
        "        ax.axhline(\n",
        "            y=emp_price,\n",
        "            color='darkviolet',\n",
        "            linestyle=':',\n",
        "            linewidth=2.5,\n",
        "            label=f'Actual Price (${emp_price:.2f})',\n",
        "        )\n",
        "\n",
        "    ax.set_title(title_str)\n",
        "    ax.set_xlabel('Quantity')\n",
        "    ax.set_ylabel('Price ($)')\n",
        "    ax.grid(True, linestyle=':', alpha=0.6)\n",
        "\n",
        "    # Set reasonable limits\n",
        "    max_qty = max(max(supply, default=0), max(demand, default=0))\n",
        "    ax.set_xlim(left=-0.5, right=max_qty + 1 if max_qty > 0 else 10)\n",
        "    ax.set_ylim(bottom=0)\n",
        "    ax.legend()\n",
        "\n",
        "  # Hide any unused subplots\n",
        "  for j in range(num_rounds, len(axes)):\n",
        "    fig.delaxes(axes[j])\n",
        "\n",
        "  fig.suptitle(f'Supply and Demand Curves for \"{good_id}\"', fontsize=16, y=1.03)\n",
        "  plt.tight_layout()\n",
        "  plt.show()\n",
        "\n",
        "\n",
        "def plot_bid_ask_spread(df, product_to_plot):\n",
        "  \"\"\"Processes market data for a specific product and plots its\n",
        "\n",
        "  bid-ask spread over time.\n",
        "\n",
        "  Args:\n",
        "      df (pd.DataFrame): The input DataFrame containing market data. Must\n",
        "        include 'good', 'round', 'day', 'price', 'demand', and 'supply' columns.\n",
        "      product_to_plot (str): The name of the product to plot.\n",
        "  \"\"\"\n",
        "\n",
        "  # --- Process the Data from the curve_history Object ---\n",
        "  processed_data = []\n",
        "  rounds = df['round'].unique()\n",
        "\n",
        "  curve_history = df[df['good'] == product_to_plot]\n",
        "\n",
        "  for round_num in rounds:\n",
        "    market_data = curve_history[curve_history['round'] == round_num]\n",
        "    if product_to_plot not in market_data['good'].values:\n",
        "      continue  # Skip if product doesn't exist in this round\n",
        "\n",
        "    prices = market_data['price'].values\n",
        "    demands = market_data['demand'].values\n",
        "    supplies = market_data['supply'].values\n",
        "\n",
        "    # Find the best bid (highest price with non-zero demand)\n",
        "    bid_prices = [price for price, demand in zip(prices, demands) if demand > 0]\n",
        "    best_bid = max(bid_prices) if bid_prices else np.nan\n",
        "\n",
        "    # Find the best ask (lowest price with non-zero supply)\n",
        "    ask_prices = [\n",
        "        price for price, supply in zip(prices, supplies) if supply > 0\n",
        "    ]\n",
        "    best_ask = min(ask_prices) if ask_prices else np.nan\n",
        "\n",
        "    # Calculate the spread\n",
        "    spread = (\n",
        "        best_ask - best_bid\n",
        "        if not np.isnan(best_bid) and not np.isnan(best_ask)\n",
        "        else np.nan\n",
        "    )\n",
        "\n",
        "    processed_data.append({\n",
        "        'Round': round_num,\n",
        "        'Best Bid': best_bid,\n",
        "        'Best Ask': best_ask,\n",
        "        'Spread': spread,\n",
        "    })\n",
        "\n",
        "  # Create a DataFrame from the processed data\n",
        "  market_df = pd.DataFrame(processed_data)\n",
        "\n",
        "  # --- 3. Plot the Data ---\n",
        "  if not market_df.empty:\n",
        "    plt.style.use('seaborn-v0_8-whitegrid')\n",
        "    fig, ax = plt.subplots(figsize=(14, 8))\n",
        "\n",
        "    # Plot the best bid and best ask lines\n",
        "    # Using .dropna() to avoid plotting gaps with straight lines\n",
        "    ax.plot(\n",
        "        market_df['Round'],\n",
        "        market_df['Best Bid'],\n",
        "        marker='o',\n",
        "        linestyle='-',\n",
        "        color='royalblue',\n",
        "        label='Best Bid',\n",
        "    )\n",
        "    ax.plot(\n",
        "        market_df['Round'],\n",
        "        market_df['Best Ask'],\n",
        "        marker='o',\n",
        "        linestyle='-',\n",
        "        color='coral',\n",
        "        label='Best Ask',\n",
        "    )\n",
        "\n",
        "    # Shade the area between the bid and ask to represent the spread\n",
        "    ax.fill_between(\n",
        "        market_df['Round'],\n",
        "        market_df['Best Bid'],\n",
        "        market_df['Best Ask'],\n",
        "        color='skyblue',\n",
        "        alpha=0.4,\n",
        "        label='Bid-Ask Spread',\n",
        "    )\n",
        "\n",
        "    # --- 4. Customize the Plot ---\n",
        "    ax.set_title(\n",
        "        f'Bid-Ask Spread by Round for\\n{product_to_plot}',\n",
        "        fontsize=16,\n",
        "        fontweight='bold',\n",
        "    )\n",
        "    ax.set_xlabel('Round', fontsize=12)\n",
        "    ax.set_ylabel('Price', fontsize=12)\n",
        "    ax.set_xticks(market_df['Round'])\n",
        "    ax.legend(fontsize=10)\n",
        "    ax.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
        "\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "  else:\n",
        "    print(\n",
        "        f\"\\nCould not find data for '{product_to_plot}' in the history object.\"\n",
        "        ' Please check the name.'\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Number of Purchases per Good"
      ],
      "metadata": {
        "id": "tmliWQQUN4im"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "trade_history = (\n",
        "    realistic_sim.game_masters[1]\n",
        "    .get_component(\n",
        "        gm_components.make_observation.DEFAULT_MAKE_OBSERVATION_COMPONENT_KEY\n",
        "    )\n",
        "    .trade_history\n",
        ")\n",
        "df_trades = pd.DataFrame(trade_history)\n",
        "\n",
        "# Generate a complete list of all goods\n",
        "all_goods_formatted = []\n",
        "for category, category_data in DETAILED_GOODS.items():\n",
        "  for quality, quality_data in category_data.items():\n",
        "    for good_name in quality_data.keys():\n",
        "      all_goods_formatted.append(good_name)\n",
        "\n",
        "# Calculate purchase counts\n",
        "successful_bids = df_trades[\n",
        "    (df_trades['transaction_occurred']) & (df_trades['side'] == 'bid')\n",
        "]\n",
        "purchase_counts = successful_bids['good'].value_counts()\n",
        "\n",
        "# Reindex to include all goods, filling missing ones with 0\n",
        "purchase_counts_all = purchase_counts.reindex(\n",
        "    all_goods_formatted, fill_value=0\n",
        ").sort_values(ascending=False)\n",
        "\n",
        "# --- Plot the Horizontal Bar Chart ---\n",
        "plt.style.use('seaborn-v0_8-whitegrid')\n",
        "plt.figure(figsize=(12, 10))  # Adjust figure size for a horizontal layout\n",
        "sns.barplot(\n",
        "    x=purchase_counts_all.values,\n",
        "    y=purchase_counts_all.index,\n",
        "    palette='viridis',\n",
        "    orient='h',\n",
        ")\n",
        "plt.xlabel('Number of Purchases', fontsize=12)\n",
        "plt.ylabel('Good', fontsize=12)\n",
        "plt.title('Total Number of Purchases per Good', fontsize=16, fontweight='bold')\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ],
      "metadata": {
        "id": "71-h9InpNlOA"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Supply Demand Curves"
      ],
      "metadata": {
        "id": "I0UIbbIqN9ra"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "B4nSuMM6g_bQ"
      },
      "outputs": [],
      "source": [
        "# The data for the supply and demand curves\n",
        "curve_history = (\n",
        "    realistic_sim.game_masters[1]\n",
        "    .get_component(\n",
        "        gm_components.make_observation.DEFAULT_MAKE_OBSERVATION_COMPONENT_KEY\n",
        "    )\n",
        "    .curve_history\n",
        ")\n",
        "\n",
        "# The data for the actual transaction prices from the simulation\n",
        "price_history = (\n",
        "    realistic_sim.game_masters[1]\n",
        "    .get_component(\n",
        "        gm_components.make_observation.DEFAULT_MAKE_OBSERVATION_COMPONENT_KEY\n",
        "    )\n",
        "    .history\n",
        ")\n",
        "\n",
        "# Choose a valid ID from your curve_history data\n",
        "good_info = producible_goods_in_category[0]\n",
        "good_to_plot = good_info[2]\n",
        "\n",
        "# Call the function with your data and the chosen ID\n",
        "plot_supply_demand_history(\n",
        "    curve_history, empirical_price_history=price_history, good_id=good_to_plot\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Bid Ask Spreads"
      ],
      "metadata": {
        "id": "QRolCs-wN_0g"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8ll8rCGn12dJ"
      },
      "outputs": [],
      "source": [
        "# Get a list of all unique products in the DataFrame\n",
        "sd_data = []\n",
        "for round_num, goods_data in curve_history.items():\n",
        "  for good_name, curve_data in goods_data.items():\n",
        "    prices = curve_data['prices']\n",
        "    supplies = curve_data['supply']\n",
        "    demands = curve_data['demand']\n",
        "    for i in range(len(prices)):\n",
        "      record = {\n",
        "          'round': round_num,\n",
        "          'good': good_name,\n",
        "          'price_point': i,\n",
        "          'price': prices[i],\n",
        "          'supply': supplies[i],\n",
        "          'demand': demands[i],\n",
        "      }\n",
        "      sd_data.append(record)\n",
        "\n",
        "# Convert list of records to DataFrame\n",
        "df_sd = pd.DataFrame(sd_data)\n",
        "all_products = df_sd['good'].unique()\n",
        "\n",
        "# Loop through each product and generate a plot\n",
        "for product in all_products:\n",
        "  plot_bid_ask_spread(df_sd, product)"
      ]
    },
    {
      "metadata": {
        "id": "S8TdyvoYdSV8"
      },
      "cell_type": "markdown",
      "source": [
        "```\n",
        "Copyright 2025 DeepMind Technologies Limited.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "you may not use this file except in compliance with the License.\n",
        "You may obtain a copy of the License at\n",
        "\n",
        "    https://www.apache.org/licenses/LICENSE-2.0\n",
        "\n",
        "Unless required by applicable law or agreed to in writing, software\n",
        "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "See the License for the specific language governing permissions and\n",
        "limitations under the License.\n",
        "```"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    },
    "language_info": {
      "name": "python"
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
